package com.ship.common.core.utils;

import com.ship.common.core.mapping.MemberValueVisitorImpl;
import javassist.*;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ConstPool;
import javassist.bytecode.annotation.*;
import lombok.extern.slf4j.Slf4j;
import tk.mybatis.mapper.entity.EntityColumn;
import tk.mybatis.mapper.mapperhelper.EntityHelper;

import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

/**
 * @author 31463
 */
@Slf4j
public class FieldUtil {

	private static String[] ignoreFields = {"serialVersionUID", "REDIS_KEY_PREFIX"};

	private static boolean shouldIgnore(Field field){
		for (String ignoreField:ignoreFields ) {
			if(ignoreField.equals(field.getName()) || (8 & field.getModifiers()) != 0){
				return true;
			}
		}
		return false;
	}

	public static List<Field> getAllFields(Class<?> c) {
		List<Field> fields = new ArrayList<Field>();
		try {
			while(!c.equals(Object.class) && !c.isAnnotation() && !c.isInterface()) {
				List<Field> list = new ArrayList<Field>();
				for (Field field : c.getDeclaredFields()) {
					if(!shouldIgnore(field) && !contains(fields, field)){
						list.add(field);
					}
				}
				c = c.getSuperclass();
				Collections.reverse(list);
				fields.addAll(list);
			}
		} catch (Exception e) {
		}
		Collections.reverse(fields);
		return fields;
	}
	
	public static Method getSetter(Field field) throws NoSuchMethodException, SecurityException {
		return field.getDeclaringClass().getDeclaredMethod("set" + StringUtil.firstLetterUpCase(field.getName()), new Class<?>[] {field.getType()});
	}

	public static Method getGetter(Field field) throws NoSuchMethodException, SecurityException {
		return field.getDeclaringClass().getDeclaredMethod("get" + StringUtil.firstLetterUpCase(field.getName()), new Class<?>[] {});
	}
	
	/**获取泛型类
	 * @param field
	 * @return
	 */
	public static Class<?> getParameterizedType(Field field){
		Type fc = field.getGenericType();
		ParameterizedType pt = (ParameterizedType) fc;  
		Class<?> genericClazz = (Class<?>)pt.getActualTypeArguments()[0];
		return genericClazz;
	}
	public static String getColumn(Field field){
		Set<EntityColumn> columns = EntityHelper.getColumns(field.getDeclaringClass());
		return getColumn(field, columns);
	}
	public static String getColumn(Field field, Set<EntityColumn> columns){
		if(columns == null) {
			return null;
		}
		for (EntityColumn entityColumn : columns) {
			if(entityColumn.getProperty().equals(field.getName())) {
				return entityColumn.getColumn();
			}
		}
		return null;
	}

	private static boolean contains(List<Field> fields , Field field) {
		for (Field field_ : fields) {
			if(field_.getName().equals(field.getName())) {
				return true;
			}
		}
		return false;
	}

	public static String getColumnByField(String fieldName, Set<EntityColumn> columns) {
		for (EntityColumn entityColumn : columns) {
			if(entityColumn.getProperty().equals(fieldName)) {
				return entityColumn.getColumn();
			}
		}
		return null;
	}
	public static Field getField(String packageName, String className, String fieldName) throws NoSuchFieldException, SecurityException, ClassNotFoundException {
		Class<?> clazz = Class.forName(packageName + "." + className);
		while(!clazz.equals(Object.class)) {
			try {
				return clazz.getDeclaredField(fieldName);
			} catch (Exception e) {
				clazz = clazz.getSuperclass();
			}
		}
		return null;
	}
	public static Field getField(Class<?> clazz, String fieldName) {
		if(clazz != null) {
			try {
				return clazz.getDeclaredField(fieldName);
			} catch (Exception e) {
			}
		}
		return null;
	}
	
	public static List<Annotation> getAnnotations(Field field, ConstPool constPool) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NotFoundException, NoSuchMethodException, SecurityException {
		List<Annotation> annotations = new ArrayList<Annotation>();
		ClassPool classPool = ClassPool.getDefault();
		for (java.lang.annotation.Annotation annotation : field.getAnnotations()) {
			CtClass annotationType = classPool.makeInterface(annotation.annotationType().getName());
			Annotation tmpAnnotation = new Annotation(constPool, annotationType) {};
			for (Method method : annotation.annotationType().getDeclaredMethods()) {
				Object value = method.invoke(annotation, new Object[] {});
				MemberValue memberValue = createMemberValue( value, constPool);
				memberValue.accept((MemberValueVisitor) new MemberValueVisitorImpl(value, constPool));
				tmpAnnotation.addMemberValue(method.getName(), memberValue);
			}
			annotations.add(tmpAnnotation);
		}
		return annotations;
	}
	
	public static MemberValue createMemberValue(Object value, ConstPool constPool) throws NotFoundException {
		MemberValue memberValue = null;
		if(value == null) {
			return null;
		}
		Class<?> type = value.getClass();
		if(isBaseType(value)) {
			try {
				type = toBaseType(type);
			} catch (ClassNotFoundException e) {
			}
		}
		if(type.equals(String.class)) {
			memberValue = new StringMemberValue((String)value, constPool);
		}else if(type.equals(Class.class)){
			memberValue = new ClassMemberValue(type.getName(), constPool);
		}else if(type.isArray()){
			memberValue = new ArrayMemberValue(constPool);
			List<MemberValue> elements = new ArrayList<MemberValue>();
			for (Object value_ : (Object[])value) {
				elements.add(createMemberValue(value_, constPool));
			}
			((ArrayMemberValue)memberValue).setValue(elements.toArray(new MemberValue[] {}));
		}else {
			memberValue = Annotation.createMemberValue(constPool, ClassPool.getDefault().get(type.getSimpleName()));
		}
		return memberValue;
	}
	
	public static boolean isBaseType(Object object) {
	    Class<?> className = object.getClass();
	    if (className.equals(Integer.class) ||
	        className.equals(Byte.class) ||
	        className.equals(Long.class) ||
	        className.equals(Double.class) ||
	        className.equals(Float.class) ||
	        className.equals(Character.class) ||
	        className.equals(Short.class) ||
	        className.equals(Boolean.class)) {
	        return true;
	    }
	    return false;
	}
	
	public static Class<?> toBaseType(Class<?> objectType) throws ClassNotFoundException {
		String[] objectTypes = {"Integer", "Byte", "Long", "Double", "Float", "Character", "Short", "Boolean"};
		Class<?>[] baseTypes = {int.class, byte.class, long.class, double.class, float.class, char.class, short.class, boolean.class};
		for (int i = 0 ; i < objectTypes.length ; i++) {
			if(objectType.getSimpleName().equals(objectTypes[i])) {
				return baseTypes[i];
			}
		}
		throw new ClassNotFoundException( objectType.getName() + " is not a bean data type");
	}
	
	public static Class<?> createClassByFields(String packageName, String className, List<Field> fields) throws CannotCompileException, RuntimeException, NotFoundException{
		return createClassByFields(packageName, className, null, fields);
	}
	
	public static Class<?> createClassByFields(String packageName, String className,Class<?> superClass, List<Field> fields) throws CannotCompileException, RuntimeException, NotFoundException{
		
		ClassPool classpool =ClassPool.getDefault();
		classpool.appendClassPath(new LoaderClassPath(FieldUtil.class.getClassLoader()));
		//创建类名
		CtClass ctClass = classpool.makeClass(packageName + "." + className);
		ConstPool constpool = ctClass.getClassFile().getConstPool();

		if(superClass != null) {
			ctClass.setSuperclass(classpool.get(superClass.getName()));
		}
		
		for (Field field : fields) {
			try {
				String fieldName = field.getName();
				NotFoundException exception = null;
				while(exception == null) {
					try {
						ctClass.getDeclaredField(fieldName);
						fieldName += "_1";
					} catch (NotFoundException e) {
						exception = e;
					}
				}
				//添加属性
				CtField ctField = CtField.make("private " + field.getType().getName() + " " + fieldName + ";", ctClass);
		        AnnotationsAttribute fieldAttr = new AnnotationsAttribute(constpool, AnnotationsAttribute.visibleTag);
		        try {
					for (Annotation annotation: FieldUtil.getAnnotations(field, constpool)) {
						fieldAttr.addAnnotation(annotation);
					}
				} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
						| NoSuchMethodException | SecurityException | NotFoundException e) {
				}
		        ctField.getFieldInfo().addAttribute(fieldAttr);
		        ctClass.addField(ctField);
		        
		        CtMethod getter = CtMethod.make("public " + field.getType().getName() + " get" + StringUtil.firstLetterUpCase(fieldName) + "(){return this." + fieldName + ";}", ctClass);
		        ctClass.addMethod(getter);
		        
		        CtMethod setter = CtMethod.make("public void set" + StringUtil.firstLetterUpCase(fieldName) +"(" + field.getType().getName() + " value){ this." + fieldName + " = value;}", ctClass);
		        ctClass.addMethod(setter);
		        
			}catch (CannotCompileException e) {
				log.error("FieldUtil.createClassByFields",e);
			}
		}
		return ctClass.toClass();
	}
}
